/**
  @file
  @author Stefan Frings
*/

#include "httpresponse.h"

using namespace qtwebapp;

HttpResponse::HttpResponse(QTcpSocket* socket)
{
	this->socket=socket;
	statusCode=200;
	statusText="OK";
	sentHeaders=false;
	sentLastPart=false;
	chunkedMode=false;
}

void HttpResponse::setHeader(QByteArray name, QByteArray value)
{
	Q_ASSERT(sentHeaders==false);
	headers.insert(name,value);
}

void HttpResponse::setHeader(QByteArray name, int value)
{
	Q_ASSERT(sentHeaders==false);
	headers.insert(name,QByteArray::number(value));
}

QMap<QByteArray,QByteArray>& HttpResponse::getHeaders()
{
	return headers;
}

void HttpResponse::setStatus(int statusCode, QByteArray description)
{
	this->statusCode=statusCode;
	statusText=description;
}

int HttpResponse::getStatusCode() const
{
	return this->statusCode;
}

void HttpResponse::writeHeaders()
{
	Q_ASSERT(sentHeaders==false);
	QByteArray buffer;
	buffer.append("HTTP/1.1 ");
	buffer.append(QByteArray::number(statusCode));
	buffer.append(' ');
	buffer.append(statusText);
	buffer.append("\r\n");
	foreach(QByteArray name, headers.keys())
	{
		buffer.append(name);
		buffer.append(": ");
		buffer.append(headers.value(name));
		buffer.append("\r\n");
	}
	foreach(HttpCookie cookie,cookies.values())
	{
		buffer.append("Set-Cookie: ");
		buffer.append(cookie.toByteArray());
		buffer.append("\r\n");
	}
	buffer.append("\r\n");
	writeToSocket(buffer);
	sentHeaders=true;
}

bool HttpResponse::writeToSocket(QByteArray data)
{
	int remaining=data.size();
	char* ptr=data.data();
	while (socket->isOpen() && remaining>0)
	{
		// If the output buffer has become large, then wait until it has been sent.
		if (socket->bytesToWrite()>16384)
		{
			socket->waitForBytesWritten(-1);
		}
		
		int written=socket->write(ptr,remaining);
		if (written==-1)
		{
			return false;
		}
		ptr+=written;
		remaining-=written;
	}
	return true;
}

void HttpResponse::write(QByteArray data, bool lastPart)
{
	Q_ASSERT(sentLastPart==false);
	
	// Send HTTP headers, if not already done (that happens only on the first call to write())
	if (sentHeaders==false)
	{
		// If the whole response is generated with a single call to write(), then we know the total
		// size of the response and therefore can set the Content-Length header automatically.
		if (lastPart)
		{
			// Automatically set the Content-Length header
			headers.insert("Content-Length",QByteArray::number(data.size()));
		}
		
		// else if we will not close the connection at the end, them we must use the chunked mode.
		else
		{
			QByteArray connectionValue=headers.value("Connection",headers.value("connection"));
			bool connectionClose=QString::compare(connectionValue,"close",Qt::CaseInsensitive)==0;
			if (!connectionClose)
			{
				headers.insert("Transfer-Encoding","chunked");
				chunkedMode=true;
			}
		}
		
		writeHeaders();
	}
	
	// Send data
	if (data.size()>0)
	{
		if (chunkedMode)
		{
			if (data.size()>0)
			{
				QByteArray size=QByteArray::number(data.size(),16);
				writeToSocket(size);
				writeToSocket("\r\n");
				writeToSocket(data);
				writeToSocket("\r\n");
			}
		}
		else
		{
			writeToSocket(data);
		}
	}
	
	// Only for the last chunk, send the terminating marker and flush the buffer.
	if (lastPart)
	{
		if (chunkedMode)
		{
			writeToSocket("0\r\n\r\n");
		}
		socket->flush();
		sentLastPart=true;
	}
}


bool HttpResponse::hasSentLastPart() const
{
	return sentLastPart;
}


void HttpResponse::setCookie(const HttpCookie& cookie)
{
	Q_ASSERT(sentHeaders==false);
	if (!cookie.getName().isEmpty())
	{
		cookies.insert(cookie.getName(),cookie);
	}
}


QMap<QByteArray,HttpCookie>& HttpResponse::getCookies()
{
	return cookies;
}


void HttpResponse::redirect(const QByteArray& url)
{
	setStatus(303,"See Other");
	setHeader("Location",url);
	write("Redirect",true);
}


void HttpResponse::flush()
{
	socket->flush();
}


bool HttpResponse::isConnected() const
{
	return socket->isOpen();
}
